/**
 * @file hs_rasterview.cpp
 * @author iam002
 * @brief RasterView模板类
 * @version 1.0
 * @date 2024-10-14
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#ifndef __HS_RASTERVIEW_H__
#define __HS_RASTERVIEW_H__

#include <limits>
#include <cmath>
#ifdef _MSC_VER 
#include <corecrt_math.h>
#include <corecrt_math_defines.h>
#endif

/* 使用 c++11 */
// #define HS_USE_CXX_STANDARD_11
#ifdef HS_USE_CXX_STANDARD_11
#include <hs_c11ext.h>
#endif

namespace hs
{

namespace raster
{


namespace padding
{

enum class model
{
    symmetric, // 图像大小通过围绕边界进行镜像反射来扩展
    replicate, // 图像大小通过复制外边界中的值来扩展
    circular,  // 通过将图像看成是一个二维周期函数的一个周期来进行扩展
    fill       // 固定值 
};

enum class direction
{
    pre,  // 在每一维的第一个元素前填充
    post, // 在每一维的最后一个元素后填充
    both  // 在每一维的第一个元素前和最后一个元素后填充
};

int _none_pre_id(int x, int Nx);
int _none_post_id(int x, int Nx);
int _sysmetric_pre_id(int x, int Nx);
int _sysmetric_post_id(int x, int Nx);
int _replicate_pre_id(int x, int Nx);
int _replicate_post_id(int x, int Nx);
int _circular_pre_id(int x, int Nx);
int _circular_post_id(int x, int Nx);

} // end namespace padding


/**
 * @brief RasterView类. 将一维数组看为二维数组(栅格), 提供二维索引访问和padding操作
 * @note 默认为行优先存储, 列有限存储使用 RasterColView 类
 * @note 若为浮点数类型, 默认返回值为NaN; 若为整型, 默认不设置无效值
 * 
 * @tparam DataType 数据类型
 */
template<typename DataType>
class RasterView
{

public:

    /**
     * @brief 默认构造函数
     * 
     */
    RasterView()
        : m_pdata(nullptr), m_size_x(0), m_size_y(0)
    { 
        setDefaultNodata();
        removePadding();
    }


    /**
     * @brief 初始化RasterView
     * 
     * @param size_x 图宽, x方向指水平方向
     * @param size_y 图高, y方向指竖直方向
     * @param pdata 数据指针
     */
    RasterView(int size_x, int size_y, DataType* pdata)
        : m_size_x(size_x), m_size_y(size_y), m_pdata(pdata)
    { 
        setDefaultNodata();
        removePadding();
    }


    /**
     * @brief 初始化RasterView
     * 
     * @param size_x 图宽, x方向指水平方向
     * @param size_y 图高, y方向指竖直方向
     * @param pdata 数据指针
     */
    void init(int size_x, int size_y, DataType* pdata)
    {
        m_pdata = pdata;
        m_size_x = size_x;
        m_size_y = size_y;
        setDefaultNodata();
        removePadding();
    }


    /**
     * @brief 返回二维数据高(y方向, 竖直方向)
     * 
     * @return int 图高
     */
    int sizeY() const
    {
        return m_size_y;
    }


    /**
     * @brief 返回二维数据宽(x方向, 水平方向)
     * 
     * @return int 图宽
     */
    int sizeX() const
    {
        return m_size_x;
    }


    /**
     * @brief 返回数据总长度
     * 
     * @return int 数据总长度
     */
    int numel() const
    {
        return m_size_x * m_size_y;
    }


    /**
     * @brief 数据是否行优先存储
     * 
     * @return true 行优先存储
     * @return false 列优先存储
     */
    virtual bool isRowMajor() const
    {
        return true;
    }


    /**
     * @brief 返回存放的数据指针
     * 
     * @return DataType* 数据指针
     */
    DataType* pData()
    {
        return m_pdata;
    }

    
    /**
     * @brief 返回存放的数据指针
     * 
     * @return DataType* 数据指针
     */
    const DataType* pData() const
    {
        return m_pdata;
    }


    /**
     * @brief 判断RasterView是否为空(即无数据指针)
     * 
     * @return true RasterView为空
     * @return false RasterView不为空
     */
    bool isEmpty() const
    {
        return m_pdata == nullptr;
    }


    /**
     * @brief 清空RasterView
     * 
     */
    void clear()
    {
        m_size_x = 0;
        m_size_y = 0;
        m_pdata = nullptr;
        removePadding();
    }

    /**
     * @brief 将平面索引(x,y)转换为线性索引
     * 
     * @param x 平面x方向索引
     * @param y 平面y方向索引
     * @return int 线性索引
     */
    virtual int sub2ind(int x, int y) const
    {
        return x + y * m_size_x;
    }


    /**
     * @brief 将线性索引转换为平面索引(x,y)
     * 
     * @param ind 线性索引
     * @param x 平面x方向索引
     * @param y 平面y方向索引
     */
    virtual void ind2sub(int ind, int*x , int* y) const
    {
        (*y) = ind / m_size_x;
        (*x) = ind - (*y) * m_size_x;
    }


    /**
     * @brief 获取平面索引坐标(x,y)上的值, 注意不检查索引是否越界
     * 
     * @param x 平面x方向索引
     * @param y 平面y方向索引
     * @return const DataType& 平面索引坐标(x,y)的取值
     */
    const DataType & operator()(int x, int y) const
    {
        // NOTE 不检查索引是否越界
        int ind = sub2ind(x, y);
        return m_pdata[ind];
    }


    /**
     * @brief 获取平面索引坐标(x,y)上的值, 注意不检查索引是否越界
     * 
     * @param x 平面x方向索引
     * @param y 平面y方向索引
     * @return DataType& 平面索引坐标(x,y)的取值
     */
    DataType & operator()(int x, int y)
    {
        // NOTE 不检查索引是否越界
        int ind = sub2ind(x, y);
        return m_pdata[ind];
    }


    /**
     * @brief 获取无效值(若未指定无效值, 此函数的返回值可能没有实际意义)
     * 
     * @param has_nodata 输出, 是否存在无效值
     * @return DataType 
     */
    DataType nodata(bool* has_nodata = nullptr) const
    {
        if (has_nodata) {
            (*has_nodata) = m_has_nodata;
        }
        return m_nodata;
    }


    /**
     * @brief 是否存在无效值
     * 
     * @return true 存在无效值, nodata()返回值有效
     * @return false 不存在无效值, nodata()返回值无效
     */
    bool hasNodata() const
    {
        return m_has_nodata;
    }


    /**
     * @brief 设置无效数据
     * 
     * @param nodata 无效数据
     */
    void setNodata(DataType nodata)
    {
        m_nodata = nodata;
        m_has_nodata = true;
    }

    
    /**
     * @brief 移除无效数据
     * @note 浮点数移除后NaN仍为无效数据
     */
    void removeNodata()
    {
        m_has_nodata = false;
        setNanNodata();
    }


    /**
     * @brief 将无效数据设置为NaN
     * @note 数据类型为浮点数才有效
     */
    void setNanNodata()
    {
        #ifdef HS_USE_CXX_STANDARD_11
        C11EXT_IF_CONSTEXPR((std::is_same<DataType, float>::value || std::is_same<DataType, double>::value)) {
            m_has_nodata = true;
            m_nodata = std::numeric_limits<DataType>::quiet_NaN();
        } C11EXT_ENDIF_CONSTEXPR
        #else
        if constexpr (std::is_same<DataType, float>::value || std::is_same<DataType, double>::value) {
            m_has_nodata = true;
            m_nodata = std::numeric_limits<DataType>::quiet_NaN();
        }
        #endif
    }


    /**
     * @brief 设置默认的无效数据
     * 
     */
    void setDefaultNodata()
    {
        #ifdef HS_USE_CXX_STANDARD_11
        C11EXT_IF_CONSTEXPR((std::is_same<DataType, float>::value || std::is_same<DataType, double>::value)) {
            m_has_nodata = true;
            m_nodata = std::numeric_limits<DataType>::quiet_NaN();
        } C11EXT_ELSE_CONSTEXPR {
            m_has_nodata = false;
            m_nodata = 0;
        } C11EXT_ENDIF_CONSTEXPR
        #else
        if constexpr (std::is_same<DataType, float>::value || std::is_same<DataType, double>::value) {
            m_has_nodata = true;
            m_nodata = std::numeric_limits<DataType>::quiet_NaN();
        }
        else {
            m_has_nodata = false;
            m_nodata = 0;
        }
        #endif
    }


    /**
     * @brief 判断输入值是否为无效数据
     * 
     * @param value 输入值
     * @return true 输入值为无效数据
     * @return false 输入值不是无效数据
     */
    bool isNodata(DataType value) const
    {
        if (!m_has_nodata) {
            return false;
        }

        bool res = false;
        #ifdef HS_USE_CXX_STANDARD_11
        C11EXT_IF_CONSTEXPR((std::is_same<DataType, float>::value || std::is_same<DataType, double>::value)) {
            if (std::isnan(m_nodata)) {
                res = std::isnan(value);
            }
            else {
                res = (value == m_nodata);
            }
        } C11EXT_ELSE_CONSTEXPR {
            res = (value == m_nodata);
        } C11EXT_ENDIF_CONSTEXPR
        return res;
        #else
        if constexpr (std::is_same<DataType, float>::value || std::is_same<DataType, double>::value) {
            if (std::isnan(m_nodata)) {
                return std::isnan(value);
            }
            else {
                return value == m_nodata;
            }
        }
        else {
            return value == m_nodata;
        }
        #endif
    }


    /**
     * @brief 设置大小, 仅改变m_size_x与m_size_y
     * 
     * @param new_size_x 
     * @param new_size_y 
     */
    void _setSize(int new_size_x, int new_size_y)
    {
        m_size_x = new_size_x;
        m_size_y = new_size_y;
    }


    /**
     * @brief 移除padding设置
     * 
     */
    void removePadding()
    {
        m_pad_size_x = 0;
        m_pad_size_y = 0;
        m_pfun_pad_pre = padding::_none_pre_id;
        m_pfun_pad_post = padding::_none_post_id;
        m_fill_value = 0;
    }


    /**
     * @brief 设置padding参数
     * 
     * @param pad_size_x x方向扩充长度(单边), 扩充长度需小于边长
     * @param pad_size_y y方向扩充长度(单边), 扩充长度需小于边长
     * @param pad_model 扩充模式
     * @param pad_direction 扩充方向
     */
    void setPadding(int pad_size_x, int pad_size_y, padding::model pad_model = padding::model::symmetric, padding::direction pad_direction = padding::direction::both, DataType fill_value = 0)
    {
        // NOTE 扩充长度不应大于等于图像边长
        if (pad_model != padding::model::fill) {
            m_pad_size_x = pad_size_x < (m_size_x - 1) ? pad_size_x : (m_size_x - 1);
            m_pad_size_y = pad_size_y < (m_size_y - 1) ? pad_size_y : (m_size_y - 1);
        }
        else {
            m_pad_size_x = pad_size_x;
            m_pad_size_y = pad_size_y;
        }
        
        m_fill_value = fill_value;

        if (pad_direction == padding::direction::both || pad_direction == padding::direction::pre) {
            if (pad_model == padding::model::circular) {
                m_pfun_pad_pre = padding::_circular_pre_id;
            }
            else if (pad_model == padding::model::replicate) {
                m_pfun_pad_pre = padding::_replicate_pre_id;
            }
            else if (pad_model == padding::model::symmetric) {
                m_pfun_pad_pre = padding::_sysmetric_pre_id;
            }
            else {
                m_pfun_pad_pre = padding::_none_pre_id;
            }
        }
        else {
            m_pfun_pad_pre = padding::_none_pre_id;
        }

        if (pad_direction == padding::direction::both || pad_direction == padding::direction::post) {
            if (pad_model == padding::model::circular) {
                m_pfun_pad_post = padding::_circular_post_id;
            }
            else if (pad_model == padding::model::replicate) {
                m_pfun_pad_post = padding::_replicate_post_id;
            }
            else if (pad_model == padding::model::symmetric) {
                m_pfun_pad_post = padding::_sysmetric_post_id;
            }
            else {
                m_pfun_pad_post = padding::_none_post_id;
            }
        }
        else {
            m_pfun_pad_post = padding::_none_post_id;
        }
    }


    /**
     * @brief 获取平面索引坐标(x,y)上的值, 进行索引合法性检查, 支持扩充索引;
     *        若通过 setPadding 设置扩充, (x,y)可以小于0或大于边长, 但是必须
     *        在合法范围内, 负责返回无效值
     * 
     * @param x 平面x方向索引
     * @param y 平面y方向索引
     * @return DataType 平面索引坐标(x,y)上的值或者为无效值
     */
    DataType at(int x, int y) const
    {
        if (isEmpty() 
            || x < -m_pad_size_x || x >= m_size_x + m_pad_size_x 
            || y < -m_pad_size_y || y >= m_size_y + m_pad_size_y )
        {
            return m_nodata;
        }

        if (x < 0) {
            x = m_pfun_pad_pre(x, m_size_x);
        }
        else if (x >= m_size_x) {
            x = m_pfun_pad_post(x, m_size_x);
        }

        if (y < 0) {
            y = m_pfun_pad_pre(y, m_size_y);
        }
        else if (y >= m_size_y) {
            y = m_pfun_pad_post(y, m_size_y);
        }

        if (x >= 0 && y >= 0 && x <= m_size_x - 1 && y <= m_size_y - 1) {
            return this->operator()(x, y);
        }
        else {
            return m_fill_value;
        }
    }

protected:

    /// @brief 图宽
    int m_size_x;

    /// @brief 图高
    int m_size_y;

    /// @brief 数据指针
    DataType* m_pdata;

    /// @brief 无效数据
    DataType m_nodata;

    /// @brief 是否有无效数据
    bool m_has_nodata; 

    /// @brief x方向扩充长度
    int m_pad_size_x;

    /// @brief y方向扩充长度
    int m_pad_size_y;

    /// @brief 负方向扩充的函数指针
    int (*m_pfun_pad_pre)(int, int);

    /// @brief 正方向扩充的函数指针
    int (*m_pfun_pad_post)(int, int);

    /// @brief 扩充填充值
    DataType m_fill_value;
};

/**
 * @brief 列优先存储的RasterView类
 * 
 * @note 继承RasterView类, 并重写了isRowMajor, sub2ind, ind2sub方法
 * 
 * @tparam DataType 数据类型
 */
template<typename DataType>
class RasterColView : public RasterView<DataType>
{

#if _WIN32
#else
protected:
    using RasterView<DataType>::RasterView;
    using RasterView<DataType>::m_size_x;
    using RasterView<DataType>::m_size_y;
    using RasterView<DataType>::m_pdata;
    using RasterView<DataType>::m_nodata;
    using RasterView<DataType>::m_has_nodata;
#endif

public:

    /**
     * @brief 数据是否行优先存储
     * 
     * @return true 行优先存储
     * @return false 列优先存储
     */
    bool isRowMajor() const
    {
        return false;
    }


    /**
     * @brief 将平面索引(x,y)转换为线性索引
     * 
     * @param x 平面x方向索引
     * @param y 平面y方向索引
     * @return int 线性索引
     */
    int sub2ind(int x, int y) const
    {
        return y + x * m_size_y;
    }


    /**
     * @brief 将线性索引转换为平面索引(x,y)
     * 
     * @param ind 线性索引
     * @param x 平面x方向索引
     * @param y 平面y方向索引
     */
    void ind2sub(int ind, int*x , int* y) const
    {
        (*x) = ind / m_size_y;
        (*y) = ind - (*x) * m_size_y;
    }

};

} // end namespae raster

} // end namespace hs

#endif
